import React from 'react'
import cssStyle from './index.module.css'
import PropTypes from 'prop-types'

function deepCompare() {
    var i, l, leftChain, rightChain
    function compare2Objects(x, y) {
        var p
        if (isNaN(x) && isNaN(y) && typeof x === 'number' && typeof y === 'number') {
            return true
        }
        if (x === y) {
            return true
        }
        if ((typeof x === 'function' && typeof y === 'function') ||
            (x instanceof Date && y instanceof Date) ||
            (x instanceof RegExp && y instanceof RegExp) ||
            (x instanceof String && y instanceof String) ||
            (x instanceof Number && y instanceof Number)) {
            return x.toString() === y.toString()
        }
        if (!(x instanceof Object && y instanceof Object)) {
            return false
        }
        // eslint-disable-next-line no-prototype-builtins
        if (x.isPrototypeOf(y) || y.isPrototypeOf(x)) {
            return false
        }
        if (x.constructor !== y.constructor) {
            return false
        }
        if (x.prototype !== y.prototype) {
            return false
        }
        if (leftChain.indexOf(x) > -1 || rightChain.indexOf(y) > -1) {
            return false
        }
        for (p in y) {
            // eslint-disable-next-line no-prototype-builtins
            if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
                return false
            } else if (typeof y[p] !== typeof x[p]) {
                return false
            }
        }
        for (p in x) {
            // eslint-disable-next-line no-prototype-builtins
            if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
                return false
            } else if (typeof y[p] !== typeof x[p]) {
                return false
            }
            switch (typeof(x[p])) {
                case 'object':
                case 'function':
                    leftChain.push(x)
                    rightChain.push(y)
                    if (!compare2Objects(x[p], y[p])) {
                        return false
                    }
                    leftChain.pop()
                    rightChain.pop()
                    break
                default:
                    if (x[p] !== y[p]) {
                        return false
                    }
                    break
            }
        }
        return true
    }
    if (arguments.length < 1) {
        return true
    }
    for (i = 1, l = arguments.length; i < l; i++) {
        leftChain = []
        rightChain = []
        if (!compare2Objects(arguments[0], arguments[i])) {
            return false
        }
    }
    return true
}

function deepClone(target) {
    let result
    if (typeof target === 'object') {
        if (Array.isArray(target)) {
            result = []
            for (let i in target) {
                result.push(deepClone(target[i]))
            }
        } else if(target===null) {
            result = null 
        } else if(target.constructor===RegExp){
            result = target
        }else {
            result = {}
            for (let i in target) {
                result[i] = deepClone(target[i])
            }
        }
    } else {
        result = target
    }
    return result
}

const context = React.createContext()

class Form extends React.Component{
    constructor(props){
        super(props)
        let validMap = {}
        for(let prop in props.rules){
            validMap[props.rules[prop].name] = {
                isValid: true,
                msg: '',
                value: deepClone(props.form[prop]),
                init: false
            }
        }
        this.state = validMap
    }
    validate(f){
        let validate = true
        this.setState(state => {
            for(let prop in state){
                if(state[prop].init){
                    let result = this.props.rules[prop](state[prop].value)
                    if(typeof result === 'boolean' && result){
                        state[prop].isValid = true
                        state[prop].msg = ''
                    }else{
                        state[prop].isValid = false
                        state[prop].msg = result
                    }
                }
            }
            for(let key in state){
                if(!state[key].isValid){
                    validate = false
                    break
                }
            }
            return state
        }, () => {
            f(validate)
        })
    }
    clearValidate(){
        this.setState(state => {
            for(let prop in state){
                state[prop].isValid = true
                state[prop].msg = ''
            }
            return state
        })
    }
    validateInit(prop, init){
        this.setState(state => {
            state[prop].init = init
            if(!init){
                state[prop].isValid = true
                state[prop].msg = ''
            }
            return state
        })
    }
    static getDerivedStateFromProps(props, state){
        let unequal = []
        for(let key in state){
            if(!deepCompare(state[key].value, props.form[key])){
                unequal.push(key)
            }
        }
        unequal.forEach(key => {
            state[key].value = deepClone(props.form[key])
            state[key].init = true
            let result = props.rules[key](props.form[key])
            if(typeof result === 'boolean' && result){
                state[key].isValid = true
                state[key].msg = ''
            }else{
                state[key].isValid = false
                state[key].msg = result
            }
        })
        return state
    }
    render(){
        let { children, ...rest } = this.props
        return(
            <context.Provider value={{
                validateInit: (prop, init) => {
                    this.validateInit(prop, init)
                },
                validMap: this.state
            }}>
                <form {...rest}>
                    { children }
                </form>
            </context.Provider>
        )
    }
}

Form.propTypes = {
    children: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
    rules: PropTypes.object,
    form: PropTypes.object
}

class Item extends React.PureComponent{
    static contextType = context
    componentWillUnmount(){
        if(this.props.prop && this.context.validMap[this.props.prop]){
            this.context.validateInit(this.props.prop, false)
        }
    }
    componentDidMount(){
        if(this.props.prop && this.context.validMap[this.props.prop]){
            this.context.validateInit(this.props.prop, true)
        }
    }
    render(){
        let { label, labelWidth, children, prop, hidePoint, full, ...rest } = this.props
        let isValid = true
        let validText = ''
        if(prop && this.context.validMap[prop]){
            isValid = this.context.validMap[prop].isValid
            validText = this.context.validMap[prop].msg
        }
        return(
            <div className={ cssStyle.item } { ...rest }>
                <span className={ (prop && !hidePoint) ? cssStyle.required : '' } style={{ width: labelWidth || 'unset' }}>{ label }</span>
                <div className={ (prop && !isValid) ? cssStyle.inValid : '' } style={{ flex: full ? 1 : 'none', position: 'relative' }}>
                    { children }
                    {
                        (prop && !isValid)
                        ?
                        <p className={ cssStyle.inValidText }>{ validText }</p>
                        :
                        null
                    }
                </div>
            </div>
        )
    }
}

Item.propTypes = {
    label: PropTypes.string,
    labelWidth: PropTypes.string,
    children: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
    prop: PropTypes.string,
    valid: PropTypes.object,
    validateInit: PropTypes.func,
    full: PropTypes.bool,
    hidePoint: PropTypes.bool
}

export {
    Form,
    Item
}